package com.block.common.interceptor;

import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import com.block.common.annotate.RequestLimit;
import com.block.common.config.RequestLimitConfig;
import com.block.common.constant.AppRspCodeConstant;
import com.block.common.service.LogFactory;
import com.block.common.util.HttpReturnUtil;
import com.block.common.exception.ApiException;
import com.block.common.util.IpUtil;
import com.block.common.util.SpelUtil;
import com.block.db.entity.AppEncrypt;
import eu.bitwalker.useragentutils.DeviceType;
import eu.bitwalker.useragentutils.OperatingSystem;
import eu.bitwalker.useragentutils.UserAgent;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class RequestLimitInterceptor implements HandlerInterceptor {
    private static final Logger log = LogFactory.getAppReq();
    private static final String WECHAT = "micromessenger";
    @Autowired
    RequestLimitConfig requestLimitConfig;
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    private static final String RATELIMITER_COUNT_PREFIX = "ratelimiter_request_count";
    private static final String RATELIMITER_EXPIRATIONTIME_PREFIX = "ratelimiter_expirationtime";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
//           Annotation annotation= AnnotationUtils.findAnnotation(handlerMethod.getClass(),RequestLimit.class);
            RequestLimit requestLimit = handlerMethod.getMethodAnnotation(RequestLimit.class);
            if (requestLimit == null) {
                return true;
            }
            boolean userAgentLimit = userAgentLimit(request, requestLimit);
            boolean ipLimit = ipLimit(request, requestLimit);
            boolean refererLimit = refererLimit(request, requestLimit);
            boolean csrfLimit = CsrfLimit(request, response, requestLimit);
            boolean ipApiCountLimit = ipApiCountLimit(request, requestLimit);
            log.info("userAgentLimit:{},ipLimit:{},refererLimit:{},csrfLimit:{},ipApiCountLimit{}", userAgentLimit, ipLimit, refererLimit, csrfLimit, ipApiCountLimit);
            if (userAgentLimit && ipLimit && refererLimit && csrfLimit && ipApiCountLimit) {
                return true;
            }
            String uri = (String) request.getAttribute("uri");
            String lang = (String) request.getAttribute("lang");
            AppEncrypt appEncrypt = (AppEncrypt) request.getAttribute("appEncrypt");
            HttpReturnUtil.returnJson(response, appEncrypt, AppRspCodeConstant.LIMIT_REQUEST_NO_AUTH, "您无权限访问此资源,请联系管理员", new Date(), uri);
            return false;
        }
        return true;
    }

    private boolean CsrfLimit(HttpServletRequest request, HttpServletResponse response, RequestLimit requestLimit) {
        String csrfHost = requestLimitConfig.getCsrfHost();
        //不需要校验csrf
        if (!requestLimit.allowedCsrf()) {
            return true;
        }
        List<String> csrfHostList = StrUtil.split(csrfHost, ',');
        if (!StrUtil.isEmpty(csrfHost)) {
            csrfHostList = StrUtil.split(csrfHost, ',');
        }
        String originHeader = request.getHeader("Origin");
        log.info("originHeader:{}", originHeader);
        if (csrfHostList.contains(originHeader)) {
            response.setHeader("Access-Control-Allow-Headers", "*");
            response.setHeader("Access-Control-Allow-Methods", "*");
            response.setHeader("Access-Control-Allow-Credentials", "true");
            response.addHeader("Access-Control-Allow-Origin", originHeader);
            return true;

        } else {
            return false;
        }
    }

    private boolean userAgentLimit(HttpServletRequest request, RequestLimit requestLimit) {
        //未开启 不需要验证
        boolean enable = requestLimit.enabledUa();
        String userAgentStr = request.getHeader("User-Agent");
        log.info("User-Agent:{}", userAgentStr);
        if (!enable) {
            return true;
        }

        UserAgent userAgent = UserAgent.parseUserAgentString(userAgentStr);
        OperatingSystem os = userAgent.getOperatingSystem();
        OperatingSystem osGroup = userAgent.getOperatingSystem().getGroup();
        DeviceType deviceType = userAgent.getOperatingSystem().getDeviceType();
        if (DeviceType.UNKNOWN.equals(deviceType)) {
            log.info("Intercepted request, uri: " + request.getRequestURI() + " Unknown device, User-Agent: " + userAgent.toString());
            return false;
        } else if (OperatingSystem.UNKNOWN.equals(os)
                || OperatingSystem.UNKNOWN_MOBILE.equals(os)
                || OperatingSystem.UNKNOWN_TABLET.equals(os)) {
            log.info("Intercepted request, uri: " + request.getRequestURI() + " Unknown OperatingSystem, User-Agent: " + userAgent.toString());
            return false;
        }
        boolean isWechat = userAgentStr.toLowerCase().contains(WECHAT);
        if (requestLimit.mustWechat() && isWechat == false) {
            return false;
        }
        if (!requestLimit.allowedLinux() && (OperatingSystem.LINUX.equals(osGroup) || OperatingSystem.LINUX.equals(os))) {
            return false;
        }
        if (!requestLimit.allowedMobile() && (DeviceType.MOBILE.equals(deviceType) || DeviceType.TABLET.equals(deviceType))) {
            return false;
        }
        if (!requestLimit.allowedPc() && DeviceType.COMPUTER.equals(deviceType)) {
            return false;
        }
        if (!requestLimit.allowedProxy() && OperatingSystem.PROXY.equals(os)) {
            return false;
        }

        return true;
    }

    /**
     * ip拦截校验
     *
     * @param request
     * @param requestLimit
     * @return
     */
    private boolean ipLimit(HttpServletRequest request, RequestLimit requestLimit) throws ApiException {
        boolean enable = requestLimit.enabledIp();
        if (!enable) {
            return true;
        }
        String ip = (String) request.getAttribute("ip");
        if (StrUtil.isEmpty(ip)) {
            ip = IpUtil.getIp(request);
        }


        String ips = requestLimitConfig.getIps();

        String limitAnIps = requestLimit.limitIps();
        log.info("ip:{},ips{},limitIp{}", ip, ips, limitAnIps);
        //ip列表为空 通过
        //任意一个配置为空 则不限制访问ip
        if (StrUtil.isEmpty(ips) || StrUtil.isEmpty(limitAnIps)) {
            return true;
        }
        List<String> ipList = StrUtil.split(ips, ',');
        if (!StrUtil.isEmpty(limitAnIps)) {
            ipList = StrUtil.split(limitAnIps, ',');
        }
        return ipList.contains(ip);

    }

    /**
     * 接口请求次数限制
     *
     * @param request
     * @param requestLimit
     * @return
     * @throws ApiException
     */
    private boolean ipApiCountLimit(HttpServletRequest request, RequestLimit requestLimit) throws ApiException {
        int expireTime = requestLimit.timeLimit();
        int limitCount = requestLimit.timeLimitCount();
        int ipLockTime = requestLimit.ipLockTime();
        int timeDayLimitCount=requestLimit.timeDayLimitCount();
//        redisTemplate.opsForValue().increment()
        String uri = request.getRequestURI();
        String ip = (String) request.getAttribute("ip");
        if (StrUtil.isEmpty(ip)) {
            ip = IpUtil.getIp(request);
        }
        if (ipLockTime != 0) {
            String exPirationkey = RATELIMITER_EXPIRATIONTIME_PREFIX.concat(uri).concat(ip);
            String requestCountKey = RATELIMITER_COUNT_PREFIX.concat(uri).concat(ip);
            Long rExpirationTime = 0L;
            Long requestCount = 0L;
            if (!stringRedisTemplate.hasKey(exPirationkey)) {
                rExpirationTime = stringRedisTemplate.opsForValue().increment(exPirationkey);
                requestCount = stringRedisTemplate.opsForValue().increment(requestCountKey);
                stringRedisTemplate.expire(exPirationkey, expireTime, TimeUnit.MILLISECONDS);
            } else {
                rExpirationTime = stringRedisTemplate.opsForValue().increment(exPirationkey);
                requestCount = stringRedisTemplate.opsForValue().increment(requestCountKey);
            }
            //一天内触犯规则多少次 锁定一天
            if (requestCount > timeDayLimitCount) {
                stringRedisTemplate.expire(requestCountKey, ipLockTime, TimeUnit.DAYS);
                log.info("接口:{} ，一天内请求触犯规则超过{}次,锁定一天,触犯规则次数{}", exPirationkey, expireTime, limitCount, requestCount);
                throw new ApiException(AppRspCodeConstant.LIMIT_REQUEST_TOMORROW_AGAIN, "异常访问,请明天再试!");
            }
            //进行锁定
            if (rExpirationTime > limitCount) {
                requestCount = stringRedisTemplate.opsForValue().increment(requestCountKey);
                log.info("接口:{} ，{}毫秒内请求次数超过{}次,当前次数:{}", exPirationkey, expireTime, limitCount,requestCount);
                throw new ApiException(AppRspCodeConstant.LIMIT_REQUEST_OFTEN_REQUEST, "访问过于频繁,请稍后再试!");

            }
        }
        return  true;
    }


    /**
     * referer拦截校验
     *
     * @param request
     * @param requestLimit
     * @return
     */
    private boolean refererLimit(HttpServletRequest request, RequestLimit requestLimit) {
        String referer = request.getHeader("referer");
        log.info("referer:{}", referer);
        boolean enable = requestLimit.enabledReferer();
        if (!enable) {
            return true;
        }
        String referers = requestLimitConfig.getHosts();
        log.info("referer:{},referers{}", referer, referers);
        if (StrUtil.isEmpty(referer)) {
            return false;
        }
        //weikong
        if (StrUtil.isEmpty(referers)) {
            return true;
        }
        List<String> refererList = StrUtil.split(referers, ',');

        for (String regStr : refererList) {
            String regex = regStr.replace("*", "\\S*").concat("\\S*");
            String context = referer;
            if (!regStr.contains("*")) {
                context = referer.replace("https://", "").replace("http://", "");
            }
            boolean reg = ReUtil.isMatch(regex, context);
            //匹配上 直接返回。未匹配上 返回false
            if (reg) {
                return true;
            }
        }
        return false;
    }
    //正则匹配


}
